package com.polaris.live.common.network

import androidx.annotation.StringRes
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.blankj.utilcode.util.StringUtils
import com.polaris.live.common.mvvm.viewmodel.BaseViewModel
import com.polaris.live.common.network.state.ResultState
import com.polaris.live.common.network.state.paresException
import com.polaris.live.common.network.state.paresResult
import com.polaris.live.common.util.LogExt
import com.polaris.live.common.util.loading.LoadingUtils
import com.polaris.live.translation.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * 描述　:BaseViewModel请求协程封装
 *
 * @author Created by 半仙 on 2023/7/31/031 16:43
 */

/**
 * net request and showLoading 不校验请求结果数据是否是成功
 *
 * @param block 请求体方法
 * @param resultState 请求回调的ResultState数据
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoading(
    block: suspend () -> BaseResponse<T>,
    resultState: MutableLiveData<ResultState<T>>,
    @StringRes message: Int,
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return requestLoading(
        block,
        resultState,
        StringUtils.getString(message),
        cancelable,
        hasShadowBg,
        cancelTime,
        waitTime
    )
}

/**
 * net request and showLoading 不校验请求结果数据是否是成功
 *
 * @param block 请求体方法
 * @param resultState 请求回调的ResultState数据
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoading(
    block: suspend () -> BaseResponse<T>,
    resultState: MutableLiveData<ResultState<T>>,
    message: String = StringUtils.getString(R.string.loading),
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return launchLoading(message, cancelable, hasShadowBg, cancelTime, waitTime) {
        invokeRequest(block, resultState)
    }
}

/**
 * net request 不校验请求结果数据是否是成功
 *
 * @param block 请求体方法
 * @param resultState 请求回调的ResultState数据
 */
fun <T> BaseViewModel.request(
    block: suspend () -> BaseResponse<T>,
    resultState: MutableLiveData<ResultState<T>>,
): Job {
    return viewModelScope.launch {
        invokeRequest(block, resultState)
    }
}

private suspend fun <T> invokeRequest(
    block: suspend () -> BaseResponse<T>,
    resultState: MutableLiveData<ResultState<T>>,
) {
    runCatching {
        block()
    }.onSuccess {
        resultState.paresResult(it)
    }.onFailure {
        resultState.paresException(it)

        LogExt.logE("--net", "request", it)
    }
}

/**
 * net request 不校验请求结果数据是否是成功
 *
 * @param block 请求体方法
 * @param resultState 请求回调的ResultState数据
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoadingNoCheck(
    block: suspend () -> T,
    resultState: MutableLiveData<ResultState<T>>,
    @StringRes message: Int,
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return requestLoadingNoCheck(
        block,
        resultState,
        StringUtils.getString(message),
        cancelable,
        hasShadowBg,
        cancelTime,
        waitTime
    )
}

/**
 * net request 不校验请求结果数据是否是成功
 *
 * @param block 请求体方法
 * @param resultState 请求回调的ResultState数据
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoadingNoCheck(
    block: suspend () -> T,
    resultState: MutableLiveData<ResultState<T>>,
    message: String = StringUtils.getString(R.string.loading),
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return launchLoading(message, cancelable, hasShadowBg, cancelTime, waitTime) {
        invokeRequestNoCheck(block, resultState)
    }
}

/**
 * net request 不校验请求结果数据是否是成功
 *
 * @param block 请求体方法
 * @param resultState 请求回调的ResultState数据
 */
fun <T> BaseViewModel.requestNoCheck(
    block: suspend () -> T,
    resultState: MutableLiveData<ResultState<T>>,
): Job {
    return viewModelScope.launch {
        invokeRequestNoCheck(block, resultState)
    }
}

private suspend fun <T> invokeRequestNoCheck(
    block: suspend () -> T,
    resultState: MutableLiveData<ResultState<T>>,
) {
    runCatching {
        block()
    }.onSuccess {
        resultState.paresResult(it)
    }.onFailure {
        resultState.paresException(it)

        LogExt.logE("--net", "request", it)
    }
}

/**
 * 过滤服务器结果，失败抛异常
 *
 * @param block 请求体方法，必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不传
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoading(
    block: suspend () -> BaseResponse<T>,
    success: (T) -> Unit,
    error: (AppException) -> Unit = {},
    @StringRes message: Int,
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return requestLoading(
        block,
        success,
        error,
        StringUtils.getString(message),
        cancelable,
        hasShadowBg,
        cancelTime,
        waitTime
    )
}

/**
 * 过滤服务器结果，失败抛异常
 *
 * @param block 请求体方法，必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不传
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoading(
    block: suspend () -> BaseResponse<T>,
    success: (T) -> Unit,
    error: (AppException) -> Unit = {},
    message: String = StringUtils.getString(R.string.loading),
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return launchLoading(message, cancelable, hasShadowBg, cancelTime, waitTime) {
        manualRequest(block, success, error)
    }
}

/**
 * 过滤服务器结果，失败抛异常
 *
 * @param block 请求体方法，必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不传
 */
fun <T> BaseViewModel.request(
    block: suspend () -> BaseResponse<T>,
    success: (T) -> Unit,
    error: (AppException) -> Unit = {},
): Job {
    return viewModelScope.launch {
        manualRequest(block, success, error)
    }
}

/**
 * 手动处理请求结果
 */
suspend fun <T> manualRequest(
    block: suspend () -> BaseResponse<T>,
    success: (T) -> Unit,
    error: (AppException) -> Unit,
) {
    runCatching {
        block()
    }.onSuccess {
        runCatching {
            executeResponse(it) { t ->
                success(t)
            }
        }.onFailure { e ->
            error(ExceptionHandler.handleException(e))

            LogExt.logE("--net", "request", e)
        }
    }.onFailure {
        error(ExceptionHandler.handleException(it))

        LogExt.logE("--net", "request", it)
    }
}

/**
 *  不过滤请求结果
 *
 * @param block 请求体 必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不给
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoadingNoCheck(
    block: suspend () -> T,
    success: (T) -> Unit,
    error: (AppException) -> Unit = {},
    @StringRes message: Int,
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return requestLoadingNoCheck(
        block,
        success,
        error,
        StringUtils.getString(message),
        cancelable,
        hasShadowBg,
        cancelTime,
        waitTime
    )
}

/**
 *  不过滤请求结果
 *
 * @param block 请求体 必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不给
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 */
fun <T> BaseViewModel.requestLoadingNoCheck(
    block: suspend () -> T,
    success: (T) -> Unit,
    error: (AppException) -> Unit = {},
    message: String = StringUtils.getString(R.string.loading),
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
): Job {
    return launchLoading(message, cancelable, hasShadowBg, cancelTime, waitTime) {
        invokeRequestNoCheck(block, success, error)
    }
}

/**
 *  不过滤请求结果
 *
 * @param block 请求体 必须要用suspend关键字修饰
 * @param success 成功回调
 * @param error 失败回调 可不给
 */
fun <T> BaseViewModel.requestNoCheck(
    block: suspend () -> T,
    success: (T) -> Unit,
    error: (AppException) -> Unit = {},
): Job {
    return viewModelScope.launch {
        invokeRequestNoCheck(block, success, error)
    }
}

private suspend fun <T> invokeRequestNoCheck(
    block: suspend () -> T,
    success: (T) -> Unit,
    error: (AppException) -> Unit,
) {
    runCatching {
        block()
    }.onSuccess {
        success(it)
    }.onFailure {
        error(ExceptionHandler.handleException(it))

        LogExt.logE("--net", "request", it)
    }
}

/**
 * 处理页面状态
 *
 * @param resultState 接口返回值
 * @param success 成功回调
 * @param error 失败回调
 */
fun <T> parseState(
    resultState: ResultState<T>,
    success: (T) -> Unit,
    error: ((AppException) -> Unit)? = null,
) {
    when (resultState) {
        is ResultState.Success -> {
            success(resultState.data)
        }

        is ResultState.Error -> {
            error?.invoke(resultState.error)
        }
    }
}

/**
 * 请求结果过滤，判断请求服务器请求结果是否成功，不成功则会抛出异常
 */
suspend fun <T> executeResponse(
    response: BaseResponse<T>,
    success: suspend CoroutineScope.(T) -> Unit,
) {
    coroutineScope {
        val result = response.getSuccessResult()
        if (result != null) {
            success(result)
        } else {
            throw AppException(
                response.getResponseCode(),
                response.getResponseMsg(),
                response.getResponseMsg(),
            )
        }
    }
}

/**
 * 默认在io线程调用协程
 *
 * @param block 操作耗时操作任务
 * @param onSuccess 成功回调
 * @param onError 失败回调 可不给
 */
fun <T> BaseViewModel.launchWithIO(
    block: suspend () -> T,
    onSuccess: (T) -> Unit,
    onError: (Throwable) -> Unit = {},
): Job {
    return viewModelScope.launch {
        runCatching {
            withContext(Dispatchers.IO) {
                block()
            }
        }.onSuccess(onSuccess)
            .onFailure(onError)
    }
}

/**
 * 显示loading并且执行
 *
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 * @param block 执行函数
 */
fun BaseViewModel.launchLoading(
    @StringRes message: Int,
    cancelable: Boolean = false,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
    useView: Boolean = true,
    block: suspend () -> Unit,
): Job {
    return launchLoading(
        StringUtils.getString(message),
        cancelable,
        hasShadowBg,
        cancelTime,
        waitTime,
        useView,
        block
    )
}

/**
 * 显示loading并且执行，默认这个是可取消的
 *
 * @param message loading的提示语
 * @param cancelable 是否可取消
 * @param hasShadowBg 是否有阴影背景
 * @param cancelTime 取消时间
 * @param waitTime 等待时间
 * @param block 执行函数
 */
fun BaseViewModel.launchLoading(
    message: String = StringUtils.getString(R.string.loading),
    cancelable: Boolean = true,
    hasShadowBg: Boolean = false,
    cancelTime: Long = 15 * 1000L,
    waitTime: Long = 500L,
    useView: Boolean = true,
    block: suspend () -> Unit,
): Job {
    var job: Job? = null
    return viewModelScope.launch {
        showLoading(message, cancelable, hasShadowBg, cancelTime, waitTime, useView, {
            job?.cancel()
        })
        try {
            block()
        } finally {
            LoadingUtils.dismissLoadingDialog()
        }
    }.apply {
        job = this
    }
}